home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 7661 / 7661.xpi / components / RILofflineQueue.js < prev    next >
Text File  |  2009-12-11  |  14KB  |  447 lines

  1. /*
  2.  
  3. License: This source code may not be used in other applications whether they
  4. be personal, commercial, free, or paid without written permission from Read It Later.
  5.  
  6.  
  7. /////////
  8. DEVELOPER API: readitlaterlist.com/api/
  9. /////////
  10.  
  11. If you would like to customize Read It Later or build an application that works with
  12. Read it Later take a look at the READ IT LATER OPEN API:
  13. http://readitlaterlist.com/api/
  14.  
  15. Suggestions for additions to Read It Later are VERY welcome.  A large number of user
  16. suggestions have been implemented.  Please let me know of any additional features you
  17. are seeking at: http://readitlaterlist.com/support/
  18.  
  19. Thanks
  20.  
  21. */
  22.  
  23. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  24.  
  25. function RILofflineQueue() {
  26.     this.wrappedJSObject = this;        
  27.     this.maxThreads = 2; // increasing this will speed up downloading but will slow down firefox performance
  28.     this.threads = [];
  29. }
  30.  
  31. // class definition
  32. RILofflineQueue.prototype = {
  33.  
  34.   // properties required for XPCOM registration:
  35.   classDescription: "Read It Later Offline Queue Javascript XPCOM Component",
  36.   classID:          Components.ID("{a6b4c920-aab2-11de-8a39-0800200c9a66}"),
  37.   contractID:       "@ril.ideashower.com/rilofflinequeue;1",
  38.   
  39.   QueryInterface: XPCOMUtils.generateQI(),
  40.  
  41.  
  42.  
  43.     //////////////////////////////////
  44.     
  45.     init : function()
  46.     {
  47.         this.APP    = Components.classes['@ril.ideashower.com/rildelegate;1'].getService().wrappedJSObject;
  48.         this.PREFS = Components.classes['@ril.ideashower.com/rilprefs;1'].getService().wrappedJSObject;
  49.         this.JSON   = Components.classes["@mozilla.org/dom/json;1"].createInstance(Components.interfaces.nsIJSON);
  50.         this.OBS = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);        
  51.     },
  52.         
  53.     start : function(resetQueue, doNotLoadQueue)
  54.     {    
  55.     
  56.     if (resetQueue && this.downloading) return -1;    
  57.     this.downloading = true;
  58.     
  59.     if (!doNotLoadQueue)
  60.         this.loadQueue(this.PREFS.getBool('getOfflineWeb'), this.PREFS.getBool('getOfflineText'), resetQueue);
  61.     
  62.         if (!this.queue || this.queue.length == 0) {
  63.         this.downloading = false;
  64.             return -2;
  65.         }
  66.     // Create threads
  67.     if (resetQueue || !this.threads || this.threads.length == 0)
  68.         this.createThreads(true);
  69.     
  70.     return true;
  71.     },
  72.     
  73.     
  74.     makeSureQueueIsInit : function(force)
  75.     {
  76.     if (force || !this.queue)
  77.     {
  78.         this.pointer = 0;    
  79.         this.counters = {success:0,failed:0};
  80.         this.queue = [];
  81.         this.idsInTheQueue = {'2':{},'1':{}};
  82.     }  
  83.     },
  84.     
  85.     loadQueue : function(downloadWeb, downloadText, resetQueue)
  86.     {
  87.     
  88.     this.makeSureQueueIsInit(resetQueue);
  89.         
  90.         let item, downloadingAtLeastOneView;
  91.     
  92.     // sort list so newest is first
  93.         let sortedList = this.APP.sortList(this.APP.LIST.list.slice());
  94.         
  95.         for(let i in sortedList)
  96.         {
  97.             downloadingAtLeastOneView = false;
  98.             item = sortedList[i];
  99.         
  100.         this.addItemToQueue(item, {web:downloadWeb,text:downloadText});
  101.         }
  102.         
  103.     },
  104.     
  105.     addItemToQueue : function(item, views, startIfNotStarted)
  106.     {
  107.     this.makeSureQueueIsInit();
  108.     
  109.     // if it's already in the queue, skip it    
  110.     if (!views) views = {web:this.PREFS.getBool('getOfflineWeb'), text:this.PREFS.getBool('getOfflineText')};
  111.     
  112.     // add specific views to queue
  113.     if (views.web && item.offlineWeb != 1 && !this.idsInTheQueue[2][item.itemId])
  114.     {
  115.         this.addToQueue(item.itemId, item.url, 2, startIfNotStarted);
  116.     }
  117.     if (views.text && item.offlineText != 1 && !this.idsInTheQueue[1][item.itemId])
  118.     {
  119.         this.addToQueue(item.itemId, item.url, 1, startIfNotStarted);
  120.     }
  121.     },
  122.     
  123.     addToQueue : function(itemId, url, type, startIfNotStarted)
  124.     {
  125.     if (this.clearingOffline) return false;
  126.     
  127.         let downloader;
  128.         
  129.         if (type == 1)
  130.             downloader = Components.classes["@ril.ideashower.com/riltextdownloader;1"].createInstance(Components.interfaces.nsIRILtextDownloader);
  131.         else if (type == 2)
  132.             downloader = Components.classes["@ril.ideashower.com/rilwebdownloader;1"].createInstance(Components.interfaces.nsIRILwebDownloader);
  133.         
  134.         if (downloader)
  135.         {
  136.             downloader.init( itemId, url );
  137.             this.queue.push( downloader );
  138.         this.idsInTheQueue[type][itemId] = true;
  139.         }
  140.     
  141.     if (startIfNotStarted) {
  142.         if (!this.downloading) return this.start(true, true);
  143.         
  144.         // else
  145.         if (!this.next()) {
  146.         this.updateProgress();
  147.         }
  148.     }
  149.         
  150.     },    
  151.     
  152.     createThreads : function(loadNextRightAway)
  153.     {
  154.     this.threads = [];
  155.     
  156.     for(let i=0; i<this.maxThreads; i++) {
  157.         this.threads[i] = {
  158.         id : i,        
  159.         inUse : false
  160.         }
  161.         
  162.         if (loadNextRightAway) this.next();
  163.     }
  164.     },
  165.     
  166.     next : function()
  167.     {
  168.         // check if still online
  169.         
  170.         let mainWindow = this.APP.getMainWindow();
  171.         if (mainWindow && mainWindow.navigator && !mainWindow.navigator.onLine)
  172.         {   // if it can't access the navigator, assume still online
  173.             this.cancel();
  174.             return;
  175.         }
  176.     
  177.     let nextItem = this.queue[this.pointer];
  178.     
  179.     if ( nextItem )
  180.     {
  181.         // make sure the item still exists (wasn't marked as read)
  182.         let item = this.APP.LIST.itemById(nextItem.itemId);
  183.         if (!item)
  184.         {
  185.         this.pointer++; //skip it
  186.         return this.next();
  187.         }
  188.         
  189.         
  190.         let thread = this.getAnOpenThread();
  191.         
  192.         if (thread) {
  193.         
  194.         this.pointer++;
  195.         this.updateProgress();
  196.         
  197.         //this.d("\nstarting "+nextItem.url);
  198.         
  199.         // load thread
  200.         thread.downloader = nextItem;
  201.                 nextItem.start(thread.id);
  202.         
  203.         return true;
  204.         
  205.         } // no threads are open, do not advance the pointer, after a thread completes it will come back here
  206.     }
  207.     else
  208.     {        
  209.         //this.d('nothing to next');
  210.         
  211.         for(let i in this.threads)
  212.         {
  213.         if (this.threads[i].inUse) {
  214.                     //dump("\n\nthread still open")
  215.             //this.d('thread '+i+ ' is still open: '+this.threads[i].downloader.itemId + ' : ' +this.threads[i].downloader.url )
  216.             return; // a thread is still processing, do not end queue yet
  217.         }
  218.         }
  219.         
  220.         // Complete
  221.             //dump("\n\n\nQUEUE IS DONE")
  222.             this.queueIsDone();
  223.         
  224.     }
  225.     
  226.     },
  227.     
  228.     getAnOpenThread : function() {
  229.     if (!this.threads) this.createThreads();
  230.     
  231.     for(let i in this.threads)
  232.     {
  233.         if (!this.threads[i].inUse) {
  234.         this.threads[i].inUse = true;
  235.         return this.threads[i];
  236.         }
  237.     }
  238.     },
  239.     
  240.     updateProgress : function(turnOff)
  241.     {
  242.     if (!this.queue) this.queue = [];
  243.         this.APP.updateDownloadProgress( turnOff?-1:this.pointer , this.queue.length );
  244.     },
  245.     
  246.     textFinished : function(downloader)
  247.     {
  248.         this.itemIsDone(downloader.itemId, downloader.type, downloader.threadId, downloader.success, downloader.statusCode);
  249.     },
  250.     
  251.     itemIsDone : function(itemId, type, threadId, success, statusCode, retainDomains)
  252.     {        
  253.         if (this.downloading)
  254.     {
  255.         // make sure the item still exists (wasn't marked as read)
  256.         let item = this.APP.LIST.itemById(itemId);
  257.         if (item)
  258.         {        
  259.         //this.d("\n\nitem is done | \n threadId:" + threadId + " \nsuccess: "  + success + " \n url: " + item.url + " \n type: " + type + "\nstatusCode: " + statusCode + ' | ' + retainDomains);
  260.         this.APP.LIST.updateOffline(itemId, type, statusCode, retainDomains);        
  261.         this.counters[ success ? 'success' : 'failed' ]++;
  262.         }
  263.         
  264.         this.threads[ threadId ].inUse = false;
  265.             this.next();
  266.     }
  267.     },
  268.     
  269.     queueIsDone : function()
  270.     {
  271.     //this.d('queueIsDone');
  272.     if (this.downloading)
  273.     {
  274.         this.pointer = this.queue.length+1; //set it to +1 over the queue length so updateProgress knows we are done
  275.         this.updateProgress();
  276.         this.downloading = false;
  277.         this.threads = {};
  278.         this.queue = null;
  279.     }
  280.     },
  281.     
  282.     cancel : function()
  283.     {
  284.     this.downloading = false;
  285.     this.updateProgress(true);
  286.     for(let i in this.threads)
  287.     {
  288.             if (this.threads[i].downloader)
  289.                 this.threads[i].downloader.cancel();
  290.     }
  291.     this.threads = false;
  292.     this.queue = null;
  293.     },
  294.     
  295.     // -- //
  296.     
  297.     downloadTextWrapper : function(itemId, url, delegate, doc)
  298.     {
  299.         if (!this.textViewDownloads) this.textViewDownloads = {};
  300.         
  301.         let set = {doc: doc, delegate: delegate};
  302.         set.downloader = Components.classes["@ril.ideashower.com/riltextdownloader;1"].createInstance(Components.interfaces.nsIRILtextDownloader);
  303.         this.textViewDownloads[set.downloader.init( itemId, url, 'textViewReady')] = set;
  304.     set.downloader.start(null);  
  305.     },
  306.     
  307.     textViewReady : function(downloader)
  308.     {
  309.         let set = this.textViewDownloads[ downloader.requestId ];
  310.         if (set.delegate)
  311.             set.delegate['textViewReady'].call(set.delegate, downloader, set.doc);
  312.     },
  313.     
  314.     
  315.     // -- Writing / Files
  316.     
  317.     write : function(path, data, noEncoding, delegate, selector)
  318.     {
  319.     
  320.         try {
  321.         
  322.         // Create file paths
  323.             let file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
  324.             file.initWithPath( path );
  325.             
  326.             // Create needed directories to file
  327.             if (!file.exists()) file.createUnique( Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0777);
  328.  
  329.  
  330.             // - Async write
  331.             
  332.             // Then, we need an output stream to our output file.
  333.             var ostream = Components.classes["@mozilla.org/network/file-output-stream;1"].
  334.                           createInstance(Components.interfaces.nsIFileOutputStream);
  335.             ostream.init(file, -1, -1, 0);
  336.             
  337.             var istream;
  338.             if (noEncoding)
  339.             {
  340.                 
  341.                 // Finally, we need an input stream to take data from.
  342.                 istream = Components.classes["@mozilla.org/io/string-input-stream;1"].
  343.                               createInstance(Components.interfaces.nsIStringInputStream);
  344.                             
  345.                 istream.setData(data, data.length);
  346.             }
  347.             
  348.             else {
  349.                 // Obtain a converter to convert our data to a UTF-8 encoded input stream.
  350.                 let converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
  351.                                 .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
  352.                 converter.charset = "UTF-8";
  353.             
  354.                 // Asynchronously copy the data to the file.
  355.                 istream = converter.convertToInputStream(data);
  356.             }
  357.             
  358.             // Start saving
  359.             this.asyncCopy(istream, ostream, this.APP.genericClosure(delegate, selector));  
  360.         
  361.         return true;
  362.         
  363.         } catch(e) {
  364.             Components.utils.reportError('Error saving file: ' + path + "\n" + e);
  365.         }
  366.     },
  367.     
  368.     // copied from http://mxr.mozilla.org/mozilla-central/source/netwerk/base/src/NetUtil.jsm#77
  369.     // which replaces https://developer.mozilla.org/en/JavaScript_code_modules/NetUtil.jsm#asyncCopy
  370.     // TODO when 3.6 is out, this will only be required for older browsers
  371.     asyncCopy: function (aSource, aSink, aCallback)
  372.     {
  373.          if (!aSource || !aSink) {
  374.              let exception = new Components.Exception(
  375.                  "Must have a source and a sink",
  376.                  Cr.NS_ERROR_INVALID_ARG,
  377.                  Components.stack.caller
  378.              );
  379.              throw exception;
  380.          }
  381.  
  382.          var sourceBuffered = true;//ioUtil.inputStreamIsBuffered(aSource);
  383.          var sinkBuffered = true;//ioUtil.outputStreamIsBuffered(aSink);
  384.  
  385.          var ostream = aSink;
  386.          if (!sourceBuffered && !sinkBuffered) {
  387.              // wrap the sink in a buffered stream.
  388.              ostream = Components.classes["@mozilla.org/network/buffered-output-stream;1"].
  389.                        createInstance(Components.interfaces.nsIBufferedOutputStream);
  390.              ostream.init(aSink, 0x8000);
  391.              sinkBuffered = true;
  392.          }
  393.  
  394.          // make a stream copier
  395.          var copier = Components.classes["@mozilla.org/network/async-stream-copier;1"].
  396.              createInstance(Components.interfaces.nsIAsyncStreamCopier);
  397.  
  398.          // Initialize the copier.  The 0x8000 should match the size of the
  399.          // buffer our buffered stream is using, for best performance.  If we're
  400.          // not using our own buffered stream, that's ok too.  But maybe we
  401.          // should just use the default net segment size here?
  402.          copier.init(aSource, ostream, null, sourceBuffered, sinkBuffered,
  403.                      0x8000, true, true);
  404.  
  405.          var observer;
  406.          if (aCallback) {
  407.              observer = {
  408.                  onStartRequest: function(aRequest, aContext) {},
  409.                  onStopRequest: function(aRequest, aContext, aStatusCode) {
  410.                     let success = (Components.isSuccessCode(aStatusCode));
  411.                     aCallback(success);
  412.                  }
  413.              }
  414.          } else {
  415.              observer = null;
  416.          }
  417.  
  418.          // start the copying
  419.          copier.asyncCopy(observer, null);
  420.          return copier;
  421.     },
  422.     
  423.     // -- //
  424.     
  425.     setOfflineStatus : function(action, state)
  426.     {
  427.         this[action+'Offline'] = state;
  428.         
  429.         // update assets directories
  430.         if (action == 'moving' && !state)
  431.             this.APP.ASSETS.init();
  432.         
  433.         // update options window
  434.         this.APP.commandInAllOpenWindows('RILoptions', 'offlineStatusChanged', null, true, true);
  435.     },
  436.     
  437.     // -- //
  438.     
  439.     d : function(str) { return dump(str+"\n"); },  
  440.      
  441. };
  442.  
  443. var components = [RILofflineQueue];
  444. function NSGetModule(compMgr, fileSpec) {
  445.   return XPCOMUtils.generateModule(components);
  446. }
  447.